﻿// <copyright file="TileListCollection.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>9/19/2009</date>
// <summary>ASCII style graphics manager using GDI+ libraries.</summary>

// License:

// Product: ASCII_GDI.csproj
// Author: Robert N. Tenney
// Copyright: 9/19/2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;

using TenneySoftware.Engine;
using TenneySoftware.Engine.GraphicsManager;

namespace TenneySoftware.GraphicsManagers {
    /// <summary>
    /// TileList class is a collection of Dictionary objects with the first letter of the tile as the key and the TileImageCollection
    /// as the value. Each TileImageCollection is a collection of all the tiles that begin with the key letter.
    /// </summary>
    [Serializable]
    public class TileListCollection : IEnumerable<TileImages> {
        /// <summary>
        /// Versioning number for this class.
        /// </summary>
        private static Version version = new Version(0, 1, 1500);

        /// <summary>
        /// List of drawable tiles and their images.
        /// </summary>
        [NonSerialized]
        private Dictionary<char, TileImageCollection> tileLists;

        /// <summary>
        /// An array of values from the Dictionary tileList. Used for serialization.
        /// </summary>
        private TileImageCollection[] values;

        /// <summary>
        /// An array of keys from the Dictionary tileList. Used for serialization.
        /// </summary>
        private char[] keys;

        /// <summary>
        /// Global: DrawableTile to search in the Find() method.
        /// </summary>
        private string search;

        /// <summary>
        /// Initializes a new instance of the TileListCollection class.
        /// </summary>
        public TileListCollection() {
            this.tileLists = new Dictionary<char, TileImageCollection>();
        }

        /// <summary>
        /// Gets the total number of tiles.
        /// </summary>
        /// <value>
        /// The total number of tiles.
        /// </value>
        public int Count {
            get {
                int result = 0;
                if (this.tileLists != null) {
                    foreach (KeyValuePair<char, TileImageCollection> kvp in this.tileLists) {
                        result += kvp.Value.Count;
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Load a saved version of TileList from the disk.
        /// </summary>
        /// <param name="fileName">Name of the file to load.</param>
        /// <returns>TileList loaded from the disk.</returns>
        public static TileListCollection Load(string fileName) {
            TileListCollection temp = null;
            Stream stream = null;
            try {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
                formatter.Binder = new VersionConfigToNamespaceAssemblyObjectBinder();
                stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None);
                Version version = (Version)formatter.Deserialize(stream);
                Debug.Assert(version == TileListCollection.version, "Version numbers must match for a success load.");
                temp = (TileListCollection)formatter.Deserialize(stream);
                temp.tileLists = new Dictionary<char, TileImageCollection>();
                for (int index = 0; index < temp.keys.GetLength(0); index++) {
                    temp.tileLists.Add(temp.keys[index], temp.values[index]);
                }
            } catch (Exception ex) {
                throw new IOException("Error" + ex.Message);
            } finally {
                if (null != stream) {
                    stream.Close();
                }
            }

            return temp;
        }

        /// <summary>
        /// Save the given TileList to disk.
        /// </summary>
        /// <param name="list">TileList to save.</param>
        /// <param name="fileName">Name to save the file to.</param>
        public static void Save(TileListCollection list, string fileName) {
            Stream stream = null;
            try {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
                stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
                list.values = new TileImageCollection[list.tileLists.Count];
                list.keys = new char[list.tileLists.Count];
                int index = 0;
                foreach (KeyValuePair<char, TileImageCollection> kvp in list.tileLists) {
                    list.values[index] = kvp.Value;
                    list.keys[index] = kvp.Key;
                    index++;
                }

                formatter.Serialize(stream, TileListCollection.version);
                formatter.Serialize(stream, list);
            } catch (Exception ex) {
                throw new IOException("Error" + ex.Message);
            } finally {
                if (null != stream) {
                    stream.Close();
                }
            }
        }

        /// <summary>
        /// Save the current TileList to disk.
        /// </summary>
        /// <param name="fileName">Name to save the file to.</param>
        public void Save(string fileName) {
            TileListCollection.Save(this, fileName);
        }

        /// <summary>
        /// Sorts each list alphabetically by Tile.ToString().
        /// </summary>
        public void Sort() {
            if (this.tileLists != null) {
                foreach (KeyValuePair<char, TileImageCollection> kvp in this.tileLists) {
                    kvp.Value.Sort();
                }
            }
        }

        /// <summary>
        /// Adds a new tile to the list. This will replace the tile if it already in the list.
        /// </summary>
        /// <param name="tile">TileImages object to add.</param>
        public void Add(TileImages tile) {
            char start = Convert.ToChar(tile.ToString().ToUpperInvariant().Substring(0, 1), CultureInfo.InvariantCulture);
            if (this.tileLists.ContainsKey(start)) {
                if (this.tileLists[start].Contains(tile)) {
                    this.tileLists.Remove(start);
                }
            } else {
                this.tileLists.Add(start, new TileImageCollection());
            }

            this.tileLists[start].Add(tile);
        }

        /// <summary>
        /// Gets the next enumeration within the lists.
        /// </summary>
        /// <returns>The next enumeration.</returns>
        public IEnumerator<TileImages> GetEnumerator() {
            foreach (KeyValuePair<char, TileImageCollection> kvp in this.tileLists) {
                foreach (TileImages tile in kvp.Value) {
                    yield return tile;
                }
            }
        }

        /// <summary>
        /// Gets the next enumerator within the lists.
        /// </summary>
        /// <returns>The next enumerator.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            foreach (KeyValuePair<char, TileImageCollection> kvp in this.tileLists) {
                foreach (TileImages tile in kvp.Value) {
                    yield return tile;
                }
            }
        }

        /// <summary>
        /// Searches through the lists to find the TileImages that match the DrawableTile.
        /// </summary>
        /// <param name="tile">DrawableTile to search for.</param>
        /// <returns>The TileImages found, or null if nothing was found.</returns>
        public TileImages Find(string tile) {
            char start = Convert.ToChar(tile.ToString().ToUpperInvariant().Substring(0, 1), CultureInfo.InvariantCulture);
            if (this.tileLists.ContainsKey(start)) {
                this.search = tile;
                return this.tileLists[start].Find(this.Search);
            }

            return null;
        }

        /// <summary>
        /// The search delegate.
        /// </summary>
        /// <param name="p">The TileImages tile to compare.</param>
        /// <returns>True if the TileImages tile equaled the search term.</returns>
        private bool Search(TileImages p) {
            return string.Compare(p.Tile, this.search, StringComparison.OrdinalIgnoreCase) == 0;
        }

        /// <summary>
        /// Class used to bind the assembly to the loaded serialized object.
        /// </summary>
        internal sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder {
            /// <summary>
            /// Binds the assembly name to a given type.
            /// </summary>
            /// <param name="assemblyName">Assembly name to bind.</param>
            /// <param name="typeName">Type to bind.</param>
            /// <returns>Type of bound assembly.</returns>
            public override Type BindToType(string assemblyName, string typeName) {
                Type typeToDeserialize = null;
                try {
                    string toAssemblyName = assemblyName.Split(',')[0];
                    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                    foreach (Assembly ass in assemblies) {
                        if (ass.FullName.Split(',')[0] == toAssemblyName) {
                            typeToDeserialize = ass.GetType(typeName);
                            break;
                        }
                    }
                } catch (System.Exception) {
                    throw;
                }

                return typeToDeserialize;
            }
        }
    }
}
